from __future__ import annotations

"""
hummingbot-api 客户端封装
-------------------------
职责：
- 封装与 hummingbot-api 的交互（账户、凭据、下单、规则等）
- 提供合理的回退策略与错误包装（HBClientError）

注意：
- `_request` 统一处理状态码与连接异常
- `list_account_connectors` 多级回退，兼容不同 hbapi 版本
"""

from typing import Any, Dict, List, Optional

import httpx
from urllib.parse import quote

from ..config import get_settings


class HBClientError(RuntimeError):
    """Raised when hummingbot-api 调用失败。"""


class HBClient:
    def __init__(self) -> None:
        self._settings = get_settings()
        self._client: Optional[httpx.AsyncClient] = None

    async def _get_client(self) -> httpx.AsyncClient:
        if self._client is None:
            self._client = httpx.AsyncClient(
                base_url=str(self._settings.hb_base_url),
                timeout=30.0,
                auth=(self._settings.hb_basic_user, self._settings.hb_basic_pass),
                follow_redirects=True,
            )
        return self._client

    async def close(self) -> None:
        if self._client:
            await self._client.aclose()
            self._client = None

    async def get_trading_rules(self, connector: str, trading_pair: str) -> Dict[str, Any]:
        resp = await self._request(
            method="GET",
            path=f"/connectors/{connector}/trading-rules",
            params={"trading_pairs": trading_pair},
        )
        data = resp.json()
        if isinstance(data, dict):
            rules = data.get("trading_rules") or data.get("data")
            if isinstance(rules, list) and rules:
                return rules[0]
            return data
        if isinstance(data, list) and data:
            return data[0]
        return {}

    async def get_order_book(self, connector: str, trading_pair: str, depth: int = 1) -> Dict[str, Any]:
        payload = {
            "connector_name": connector,
            "trading_pair": trading_pair,
            "depth": depth,
        }
        resp = await self._request(
            "POST",
            "/market-data/order-book",
            json=payload,
        )
        return resp.json()

    async def place_order(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        resp = await self._request("POST", "/trading/orders", json=payload)
        return resp.json()

    async def get_trades(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        """查询成交记录：调用 hummingbot-api POST /trading/trades。"""
        resp = await self._request("POST", "/trading/trades", json=payload)
        return resp.json()

    async def search_orders(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        """查询订单历史：调用 hummingbot-api POST /trading/orders/search。"""
        resp = await self._request("POST", "/trading/orders/search", json=payload)
        return resp.json()

    async def get_portfolio_state(self, account_name: str) -> Optional[Dict[str, Any]]:
        resp = await self._request(
            "GET",
            f"/accounts/{account_name}/portfolio",
            acceptable_status={404},
        )
        if resp.status_code == 404:
            return None
        return resp.json()

    async def get_connector_portfolio_state(self, account_name: str, connector_name: str) -> Optional[Dict[str, Any]]:
        """查询指定账户在单一交易所下的组合状态。

        标准实现：调用 hummingbot-api `POST /portfolio/state`
        请求体：{"account_names":[...], "connector_names":[...]}
        响应：按账户名 → 交易所名组织的数据结构，函数将提取指定账户+交易所的内容返回。
        """
        if not account_name or not connector_name:
            raise HBClientError("账户名或交易所名称不能为空")

        resp = await self._request(
            "POST",
            "/portfolio/state",
            json={
                "account_names": [account_name],
                "connector_names": [connector_name],
            },
            acceptable_status={404},
        )
        if resp.status_code == 404:
            return None
        data = resp.json()
        if not isinstance(data, dict):
            return None
        # 兼容不同键名大小写
        for acct_key in list(data.keys()):
            if isinstance(acct_key, str) and acct_key.lower() == account_name.lower():
                acct_block = data.get(acct_key)
                if isinstance(acct_block, dict):
                    # 直接命中键
                    if connector_name in acct_block:
                        return acct_block.get(connector_name)  # 可能是 list 或 dict
                    # 不区分大小写匹配
                    lowered = connector_name.lower()
                    for k, v in acct_block.items():
                        if isinstance(k, str) and k.lower() == lowered:
                            return v
                # 有些实现会将列表放在 accounts[0] 或 connectors[0] 下
                if isinstance(acct_block, list) and acct_block:
                    try:
                        first = acct_block[0]
                        if isinstance(first, dict):
                            return first.get(connector_name) or first
                    except Exception:  # noqa: BLE001
                        pass
        return None

    async def get_accounts(self) -> Dict[str, Any]:
        resp = await self._request(
            "GET",
            "/accounts",
            acceptable_status={404},
        )
        if resp.status_code == 404:
            return []
        return resp.json()

    async def list_account_connectors(self, account_name: str) -> List[str]:
        # 优先调用 hummingbot-api: GET /accounts/{account_name}/credentials
        try:
            resp = await self._request(
                "GET",
                f"/accounts/{quote(account_name, safe='')}/credentials",
                acceptable_status={404},
            )
            if resp.status_code != 404:
                data = resp.json()
                if isinstance(data, list):
                    return list({str(x) for x in data if x})
                if isinstance(data, dict):
                    arr = data.get("connectors")
                    if isinstance(arr, list):
                        return list({str(x) for x in arr if x})
        except HBClientError:
            # 忽略，回退到旧逻辑
            pass

        # 二次回退：直接用完整 URL 访问（规避 base_url/path 兼容差异）
        try:
            async with httpx.AsyncClient(timeout=10.0) as client:
                url = f"{self._settings.hb_base_url}/accounts/{quote(account_name, safe='')}/credentials"
                r = await client.get(url, auth=(self._settings.hb_basic_user, self._settings.hb_basic_pass))
                if r.status_code == 200:
                    d = r.json()
                    if isinstance(d, list):
                        return list({str(x) for x in d if x})
                    if isinstance(d, dict) and isinstance(d.get('connectors'), list):
                        return list({str(x) for x in d['connectors'] if x})
        except Exception:
            pass

        # 回退：从 /accounts 聚合（兼容老版本 hbapi）
        accounts = await self.get_accounts()
        items = accounts if isinstance(accounts, list) else accounts.get("accounts", [])
        connectors: List[str] = []
        for item in items:
            if isinstance(item, dict):
                name = (item.get("name") or item.get("account_name") or "").lower()
                if not name or name != account_name.lower():
                    continue
                value = item.get("connector")
                if value:
                    connectors.append(str(value))
                extra = item.get("connectors")
                if isinstance(extra, list):
                    connectors.extend(str(conn) for conn in extra if conn)
        return list({c for c in connectors if c})

    async def create_account(self, account_name: str) -> bool:
        if not account_name:
            raise HBClientError("账户名称不能为空")
        resp = await self._request(
            "POST",
            "/accounts/add-account",
            params={"account_name": account_name},
            acceptable_status={400},
        )
        if resp.status_code == 400:
            detail: Optional[str] = None
            try:
                data = resp.json()
                if isinstance(data, dict):
                    detail = str(data.get("detail") or "")
            except Exception:  # noqa: BLE001
                detail = None
            if detail and "exists" in detail.lower():
                return False
            raise HBClientError(detail or "创建交易账户失败")
        return True

    async def add_credentials(self, account_name: str, connector_name: str, credentials: Dict[str, Any]) -> None:
        if not account_name or not connector_name:
            raise HBClientError("账户名或交易所名称不能为空")
        if not isinstance(credentials, dict) or not credentials:
            raise HBClientError("请提供有效的交易所凭据")
        resp = await self._request(
            "POST",
            f"/accounts/add-credential/{account_name}/{connector_name}",
            json=credentials,
            acceptable_status={400},
        )
        if resp.status_code == 400:
            detail = None
            try:
                data = resp.json()
                if isinstance(data, dict):
                    detail = str(data.get("detail") or "")
            except Exception:  # noqa: BLE001
                detail = None
            raise HBClientError(detail or "绑定交易所凭据失败")

    async def add_credentials_multiple(
        self,
        account_name: str,
        connector_payloads: Dict[str, Dict[str, Any]],
    ) -> Dict[str, str | None]:
        results: Dict[str, str | None] = {}
        for name, payload in connector_payloads.items():
            if not name or not payload:
                continue
            try:
                await self.add_credentials(account_name, name, payload)
                results[name] = None
            except HBClientError as exc:
                results[name] = str(exc)
        return results

    async def remove_credentials(self, account_name: str, connector_name: str) -> None:
        """移除账户下指定交易所的凭据。

        兼容多种 hummingbot-api 实现：
        - POST   /accounts/delete-credential/{account}/{connector}  ← 优先使用（与你提供的文档一致）
        - DELETE /accounts/remove-credential/{account}/{connector}
        - POST   /accounts/remove-credential/{account}/{connector}
        - POST   /accounts/{account}/credentials/remove  body:{connector_name}
        - 若返回 400 且提示不存在，视为幂等成功
        """
        if not account_name or not connector_name:
            raise HBClientError("账户名或交易所名称不能为空")

        # 0) 优先：POST /accounts/delete-credential/{account}/{connector}
        try:
            resp = await self._request(
                "POST",
                f"/accounts/delete-credential/{quote(account_name, safe='')}/{quote(connector_name, safe='')}",
                acceptable_status={400, 404},
            )
            if resp.status_code in {200, 204, 404}:
                return
            if resp.status_code == 400:
                try:
                    data = resp.json()
                    detail = str(data.get("detail") or "") if isinstance(data, dict) else ""
                except Exception:  # noqa: BLE001
                    detail = ""
                if detail and ("not found" in detail.lower() or "不存在" in detail):
                    return
                raise HBClientError(detail or "解绑交易所凭据失败")
        except HBClientError:
            pass

        # 1) 优先 DELETE 语义
        try:
            resp = await self._request(
                "DELETE",
                f"/accounts/remove-credential/{quote(account_name, safe='')}/{quote(connector_name, safe='')}",
                acceptable_status={400, 404},
            )
            if resp.status_code in {200, 204, 404}:
                return
            if resp.status_code == 400:
                try:
                    data = resp.json()
                    detail = str(data.get("detail") or "") if isinstance(data, dict) else ""
                except Exception:  # noqa: BLE001
                    detail = ""
                if detail and ("not found" in detail.lower() or "不存在" in detail):
                    return
                raise HBClientError(detail or "解绑交易所凭据失败")
        except HBClientError:
            pass

        # 2) 回退 POST 同路径
        try:
            resp = await self._request(
                "POST",
                f"/accounts/remove-credential/{quote(account_name, safe='')}/{quote(connector_name, safe='')}",
                acceptable_status={400, 404},
            )
            if resp.status_code in {200, 204, 404}:
                return
            if resp.status_code == 400:
                try:
                    data = resp.json()
                    detail = str(data.get("detail") or "") if isinstance(data, dict) else ""
                except Exception:  # noqa: BLE001
                    detail = ""
                if detail and ("not found" in detail.lower() or "不存在" in detail):
                    return
                raise HBClientError(detail or "解绑交易所凭据失败")
        except HBClientError:
            pass

        # 3) 回退 body 形式
        try:
            resp = await self._request(
                "POST",
                f"/accounts/{quote(account_name, safe='')}/credentials/remove",
                json={"connector_name": connector_name},
                acceptable_status={400, 404},
            )
            if resp.status_code in {200, 204, 404}:
                return
            if resp.status_code == 400:
                try:
                    data = resp.json()
                    detail = str(data.get("detail") or "") if isinstance(data, dict) else ""
                except Exception:  # noqa: BLE001
                    detail = ""
                if detail and ("not found" in detail.lower() or "不存在" in detail):
                    return
                raise HBClientError(detail or "解绑交易所凭据失败")
        except HBClientError as exc:
            raise HBClientError("无法连接 hummingbot-api，请确认服务是否可达。") from exc

    async def _request(
        self,
        method: str,
        path: str,
        *,
        acceptable_status: Optional[set[int]] = None,
        **kwargs: Any,
    ) -> httpx.Response:
        client = await self._get_client()
        try:
            response = await client.request(method, path, **kwargs)
            if acceptable_status and response.status_code in acceptable_status:
                return response
            response.raise_for_status()
            return response
        except httpx.HTTPStatusError as exc:
            status_code = exc.response.status_code if exc.response else "unknown"
            # 尽量带上 hb-api 的错误详情，便于排查
            detail = None
            try:
                data = exc.response.json()
                if isinstance(data, dict):
                    detail = data.get("detail") or data.get("message") or data
            except Exception:  # noqa: BLE001
                try:
                    detail = exc.response.text
                except Exception:  # noqa: BLE001
                    detail = None
            msg = f"hummingbot-api 返回异常状态码: {status_code}"
            if detail:
                msg += f", detail: {detail}"
            raise HBClientError(msg) from exc
        except httpx.HTTPError as exc:
            raise HBClientError("无法连接 hummingbot-api，请确认服务是否可达。") from exc


hb_client = HBClient()
